/**
 * Copyright (c) 2010-2011 Pierre Labatut
 *
 * This file is part of Space-Chronicles project.
 *
 * Space-Chronicles is free software: you can redistribute it and/or modify it
 * under the terms of its license which may be found in the accompanying
 * LICENSE file or <http://code.google.com/p/space-chronicles/>.
 */

package space.gl;

import java.io.IOException;
import javax.media.opengl.GL;
import javax.media.opengl.GL2;
import javax.media.opengl.GL3;

/**
 *
 * @author Pierre
 */
public class FrameBufferObject {

    final static int PIXEL_TYPE = GL.GL_DEPTH_COMPONENT24;
    final int FBOHandle;
    final int RBHandle;
    final int textureHandle;
    final int width;
    final int height;

    public FrameBufferObject(final GL3 gl, final int width, final int height) {
        if (gl == null) {
            throw new IllegalArgumentException();
        }
        this.width = width;
        this.height = height;
        /**
         * Frame buffer
         */
        //Allocate an new framebuffer handle
        FBOHandle = genFrameBufferObject(gl);
        //Create and bind the frame buffer for read & write ops
        gl.glBindFramebuffer(GL.GL_FRAMEBUFFER, FBOHandle);

        /**
         * Render buffer
         */
        RBHandle = genRenderBuffer(gl);
        gl.glBindRenderbuffer(GL.GL_RENDERBUFFER, RBHandle);
        gl.glRenderbufferStorage(GL.GL_RENDERBUFFER, PIXEL_TYPE, width, height);
        gl.glFramebufferRenderbuffer(GL3.GL_FRAMEBUFFER,
                GL.GL_DEPTH_ATTACHMENT,
                GL.GL_RENDERBUFFER, RBHandle);
        gl.glDrawBuffer(GL.GL_NONE);
        gl.glReadBuffer(GL.GL_NONE);
        int status = gl.glCheckFramebufferStatus(GL3.GL_DRAW_FRAMEBUFFER);
        switch (status) {
            case GL.GL_FRAMEBUFFER_COMPLETE:
                break;
            /*The framebuffer and its attachments match the
            rendering or reading state required.*/
            case GL3.GL_FRAMEBUFFER_UNDEFINED:
                throw new RuntimeException("The bound framebuffer is specified to be the "
                        + "default framebuffer (i.e., glBindFramebuffer()"
                        + "with zero specified as the framebuffer), and the "
                        + "default framebuffer doesn’t exist.");
            case GL3.GL_FRAMEBUFFER_INCOMPLETE_ATTACHMENT:
                throw new RuntimeException("A necessary attachment to the bound"
                        + "framebuffer is uninitialized");
            case GL.GL_FRAMEBUFFER_INCOMPLETE_MISSING_ATTACHMENT:
                throw new RuntimeException("There are no images (e.g., texture layers or"
                        + "renderbuffers) attached to the framebuffer.");
            case GL3.GL_FRAMEBUFFER_INCOMPLETE_DRAW_BUFFER:
                throw new RuntimeException("Every drawing buffer (e.g., GL_DRAW_BUFFERi"
                        + "as specified by glDrawBuffers()) has an"
                        + "attachment.");
            case GL3.GL_FRAMEBUFFER_INCOMPLETE_READ_BUFFER:
                throw new RuntimeException("An attachment exists for the buffer specified"
                        + "for the buffer specified by glReadBuffer().");
            case GL.GL_FRAMEBUFFER_UNSUPPORTED:
                throw new RuntimeException("The combination of images attached to the"
                        + "framebuffer object is incompatible with the"
                        + "requirements of the OpenGL implementation.");
            case GL3.GL_FRAMEBUFFER_INCOMPLETE_MULTISAMPLE:
                throw new RuntimeException("The number of samples for all images across"
                        + "the framebuffer’s attachments do not match.");
            default:
        }

        /**
         * Texture
         */
        textureHandle = genTexture(gl);
        gl.glBindTexture(GL.GL_TEXTURE_2D, textureHandle);
        gl.glTexImage2D(GL.GL_TEXTURE_2D, 0, PIXEL_TYPE, width, height, 0,
                GL3.GL_DEPTH_COMPONENT, GL3.GL_UNSIGNED_INT, null);
        gl.glFramebufferTexture2D(GL.GL_FRAMEBUFFER, GL.GL_DEPTH_ATTACHMENT,
                GL.GL_TEXTURE_2D, textureHandle, 0);


        gl.glTexParameteri(GL2.GL_TEXTURE_2D, GL2.GL_TEXTURE_MIN_FILTER,
                GL2.GL_LINEAR);
        gl.glTexParameteri(GL2.GL_TEXTURE_2D, GL2.GL_TEXTURE_MAG_FILTER,
                GL2.GL_NEAREST);
        gl.glTexParameteri(GL2.GL_TEXTURE_2D, GL2.GL_TEXTURE_WRAP_S,
                GL2.GL_CLAMP_TO_EDGE);
        gl.glTexParameteri(GL2.GL_TEXTURE_2D, GL2.GL_TEXTURE_WRAP_T,
                GL2.GL_CLAMP_TO_EDGE);
        gl.glTexParameteri(GL2.GL_TEXTURE_2D, GL2.GL_TEXTURE_COMPARE_MODE,
                GL2.GL_NONE);
        gl.glTexParameteri(GL2.GL_TEXTURE_2D, GL2.GL_DEPTH_TEXTURE_MODE,
                GL2.GL_LUMINANCE);

        gl.glBindFramebuffer(GL.GL_FRAMEBUFFER, 0);
        gl.glBindRenderbuffer(GL.GL_RENDERBUFFER, 0);
    }

    public void pushFBORenderingState(GL2 gl) {
        gl.glBindFramebuffer(GL3.GL_FRAMEBUFFER, FBOHandle);
        gl.glPushAttrib(GL3.GL_VIEWPORT);
        gl.glViewport(0, 0, width, height);
    }

    public void popFBORenderingState(GL2 gl) {
        gl.glPopAttrib();
        gl.glBindFramebuffer(GL.GL_FRAMEBUFFER, 0);
    }

    public void bindTexture(GL2 gl) {
        gl.glBindTexture(GL.GL_TEXTURE_2D, textureHandle);
    }

    private int genFrameBufferObject(GL3 gl) {
        int[] array = new int[1];
        gl.glGenFramebuffers(1, array, 0);
        return array[0];
    }

    private int genTexture(GL gl) {
        final int[] array = new int[1];
        gl.glGenTextures(1, array, 0);
        return array[0];
    }

    private int genRenderBuffer(GL gl) {
        int[] array = new int[1];
        gl.glGenRenderbuffers(1, array, 0);
        return array[0];
    }

    public void dispose(GL gl) throws IOException {
        gl.glDeleteRenderbuffers(1, new int[]{RBHandle}, 0);
        gl.glDeleteFramebuffers(1, new int[]{FBOHandle}, 0);
    }
}
